/*
 * NvmFileSource.cpp
 *
 *  Created on: 2019-01-02
 *      Author: chuanjiang.zh
 */

#include "NvmFileSource.h"
#include "CodecIDConvert.h"
#include "CLog.h"
#include <assert.h>


namespace av
{

const char*	NvmFileSource::FILE_EXT = ".nvm";

NvmFileSource::NvmFileSource():
    m_curTime(),
    m_parser(),
    m_pContext(),
    m_buffer(),
    m_videoStartTs(AV_NOPTS_VALUE),
    m_audioStartTs(AV_NOPTS_VALUE)
{

}

NvmFileSource::~NvmFileSource()
{
    close();
}


int NvmFileSource::open(const std::string& url, const std::string& params)
{
    int ret = m_reader.open(url.c_str());
    if (ret == 0)
    {
        const MediaFileHeader& header = m_reader.getFileHeader();
        m_format.m_duration = m_reader.getDuration();

        m_format.m_codec = getCodec(header.videoCodec);
        m_format.m_width = header.width;
        m_format.m_height = header.height;
        m_format.m_framerate = header.fps;

        m_format.m_channels = header.audioChannel;
        m_format.m_sampleRate = header.audioSampleRate;
        m_format.m_audioCodec = header.autoCodec;

        openParser(m_format.m_codec);
    }
    return ret;
}

void NvmFileSource::close()
{
    m_reader.close();
    m_curTime = 0;
}

bool NvmFileSource::isOpen()
{
    return m_reader.isOpen();
}

bool NvmFileSource::getFormat(MediaFormat& fmt)
{
    fmt = m_format;
    return true;
}

int NvmFileSource::getDuration()
{
    return m_format.m_duration;
}

int NvmFileSource::play()
{
    return 0;
}

int NvmFileSource::pause()
{
    return 0;
}

void NvmFileSource::stop()
{
    //
}

int NvmFileSource::getState()
{
    return STATE_PLAYING;
}

bool NvmFileSource::seekable()
{
    return true;
}

int NvmFileSource::seek(int64_t offset)
{
    m_reader.setPos(offset);

    return 0;
}

int64_t NvmFileSource::getTime()
{
    return m_curTime;
}

int NvmFileSource::setScale(float scale)
{
    return 0;
}

float NvmFileSource::getScale()
{
    return 1.0;
}

int NvmFileSource::read(AVPacket& pkt)
{
    int ret = AVERROR_EOF;
    while (true)
    {
        MediaChunkHeader header;
        memset(&header, 0, sizeof(header));
        if (!m_reader.lookaside(header))
        {
            break;
        }

        m_buffer.ensure(header.length);
        if (0 != m_reader.read(m_buffer.data(), header.length, header))
        {
            break;
        }

        int64_t ts = header.timestamp * 1000;

        AVPacket src;
        av_init_packet(&src);

        if (header.tag == CHUNK_TAG_VIDEOPARAM || header.tag == CHUNK_TAG_VIDEODATA)
        {
            src.stream_index = 0;
        }
        else
        {
            src.stream_index = 1;
        }

        src.dts = ts;
        src.pts = ts;
        src.data = m_buffer.data();
        src.size = header.length;
        //src.duration
        src.flags = 0;

        if (parseRead(src, pkt))
        {
            //CLog::debug("dts: %lld, pts: %lld, duration: %lld\n", pkt.dts, pkt.pts, pkt.duration);

            m_curTime = pkt.dts;

            ret = 0;
            break;
        }
    }

    return ret;
}

void NvmFileSource::interrupt()
{
    // pass
}

bool NvmFileSource::isLive()
{
    return false;
}

bool NvmFileSource::getMetaData(const std::string& key, std::string& value)
{
    return false;
}

av::MediaCodec NvmFileSource::getCodec(int codec)
{
    if (codec == STANDARD_H264_CODEC)
    {
        return MEDIA_CODEC_H264;
    }
    else if (codec == STANDARD_MPEG4_CODEC)
    {
        return MEDIA_CODEC_MPEG4;
    }
    else if (codec == STANDARD_H265_CODEC)
    {
        return MEDIA_CODEC_HEVC;
    }
    else if (codec == HIK_MEDIA_CODEC)
    {
        return MEDIA_CODEC_H264;
    }
    else if (codec == DH_MEDIA_CODEC)
    {
        return MEDIA_CODEC_H264;
    }
    else if (codec == G711U_CODEC)
    {
        return MEDIA_CODEC_G711U;
    }
    else if (codec == G711A_CODEC)
    {
        return MEDIA_CODEC_G711A;
    }
    else if (codec == AAC_ADTS_CODEC)
    {
        return MEDIA_CODEC_AAC;
    }
    return MEDIA_CODEC_NONE;
}


bool NvmFileSource::openParser(int codec)
{
    AVCodecID codecID = CodecIDConvert::toAVCodecID(codec);
    m_parser = av_parser_init(codecID);
    if (m_parser == NULL)
    {
        return false;
    }

    AVCodec* pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
    m_pContext = avcodec_alloc_context3(pCodec);

    int ret = avcodec_open2(m_pContext, pCodec, NULL);
    if (ret < 0)
    {
        avcodec_close(m_pContext);
        av_free(m_pContext);
        m_pContext = NULL;
        pCodec = NULL;

        av_parser_close(m_parser);
        m_parser = NULL;

        return false;
    }

    return isParserOpen();
}

void NvmFileSource::closeParser()
{
    if (m_pContext != NULL)
    {
        avcodec_close(m_pContext);
        av_free(m_pContext);
        m_pContext = NULL;
    }

    if (m_parser != NULL)
    {
        av_parser_close(m_parser);
        m_parser = NULL;
    }
}

bool NvmFileSource::isParserOpen()
{
    return (m_parser != NULL);
}

bool NvmFileSource::parseRead(AVPacket& src, AVPacket& outPkt)
{
    bool got = false;
    if (isParserOpen())
    {
        uint8_t* buffer = src.data;
        int length = src.size;

        int pos = 0;
        int64_t vpts = src.pts;
        int64_t vdts = src.pts;

        do
        {
            uint8_t *pout = NULL;
            int poutLen = 0;
            int frameLen = av_parser_parse2(m_parser, m_pContext,
                &pout,
                &poutLen,
                buffer + pos,
                length - pos,
                vpts,
                vdts,
                src.pts);

            pos += frameLen;

            if (poutLen > 0)
            {
                AVPacket packet;
                av_init_packet(&packet);
                packet.data = pout;
                packet.size = poutLen;
                packet.pts = vpts;
                packet.dts = packet.pts;
                packet.stream_index = src.stream_index;

                got = readPacket(packet, outPkt);
            }

        } while (pos < length);

        return got;
    }
    else
    {
        return readPacket(src, outPkt);
    }
}

bool NvmFileSource::readPacket(AVPacket& src, AVPacket& pkt)
{
    if (pkt.data == NULL)
    {
        av_new_packet(&pkt, src.size);
    }
    else
    {
        av_grow_packet(&pkt, src.size);
    }

    av_packet_copy_props(&pkt, &src);
    //pkt.dts = src.dts;
    //pkt.pts = src.pts;
    //pkt.duration = src.duration;
    //pkt.flags = src.flags;

    if (src.size > 0)
    {
        memcpy(pkt.data, src.data, src.size);
    }

    if (pkt.stream_index == 0)
    {
        if (AV_NOPTS_VALUE == m_videoStartTs)
        {
            m_videoStartTs = pkt.dts;
        }

        pkt.dts = pkt.dts - m_videoStartTs;
        pkt.pts = pkt.pts - m_videoStartTs;
    }
    else
    {
        if (AV_NOPTS_VALUE == m_audioStartTs)
        {
            m_audioStartTs = pkt.dts;
        }

        pkt.dts = pkt.dts - m_audioStartTs;
        pkt.pts = pkt.pts - m_audioStartTs;
    }

    return true;
}


} /* namespace av */
